export interface GenerateConfig {
    intentSpacesNumber?: number,
    newLine?: string,
    generateExample?: boolean,
    rootClassName?: string,
}
const toPascalCase = (s: string): string => {
    if (s.length < 0) {
        return s
    }
    return s[0].toUpperCase() + s.slice(1)
}
const generateFromJson = (json: any, config: GenerateConfig = {}): string => {
    const intentSpacesNumber = config.intentSpacesNumber || 4
    const intentSpaces = ' '.repeat(intentSpacesNumber)
    const newLine = config.newLine || '\r\n'
    const rootClassName = config.rootClassName || 'Result'

    let res: string[] = []
    let classes = new Set<string>()

    const getType = (key: string, value: any): string => {
        switch (typeof value) {
            case 'object':
                if (value === null || value === undefined) {
                    return 'Object'
                }

                if (Array.isArray(value)) {
                    if (value.length < 1) {
                        return 'Array<Object>'
                    }
                    return `Array<${getType(key, value[0])}>`
                }

                const typeName = toPascalCase(key)
                addClass(value, typeName)
                return typeName
            case 'number':
                if (Number.isInteger(value)) {
                    return 'Int64'
                } else {
                    return 'Float64'
                }
            case 'boolean':
                return 'Bool'
            case 'string':
                return 'String'
            default:
                return '?Object'
        }
    }
    const addClass = (obj: any, className: string) => {
        console.log(obj, className)
        if (classes.has(className)) {
            return
        }
        classes.add(className)
        let temp = `public class ${className} <: Serializable<${className}> {${newLine}`
        
        // 主构造函数
        temp += `${intentSpaces}public ${className}(${newLine}`
        temp += Object.keys(obj).map(k => {
            let prop = ''
            prop += intentSpaces
            prop += intentSpaces

            prop += `public var ${k}: `
            prop += getType(k, obj[k])
            
            return prop
        }).join(`,${newLine}`)
        temp += `${newLine}${intentSpaces}) {}${newLine}`

        // 添加序列化方法
        temp += `${intentSpaces}public func serialize(): DataModel {${newLine}`
        temp += `${intentSpaces}${intentSpaces}DataModelStruct()${newLine}`
        temp += Object.keys(obj).map(k => {
            let prop = ''
            prop += intentSpaces
            prop += intentSpaces
            prop += intentSpaces
            prop += `.add(field<${getType(k, obj[k])}>("${k}", ${k}))`
            return prop
        }).join(`${newLine}`)
        temp += `${newLine}${intentSpaces}}${newLine}`  

        // 添加反序列化方法
        temp += `${intentSpaces}public static func deserialize(dm: DataModel): ${className} {${newLine}`
        temp += `${intentSpaces}${intentSpaces}let dms = match (dm) {${newLine}`
        temp += `${intentSpaces}${intentSpaces}${intentSpaces}case d: DataModelStruct => d${newLine}`
        temp += `${intentSpaces}${intentSpaces}${intentSpaces}case _ => throw Exception("this data is not DataModelStruct")${newLine}`
        temp += `${intentSpaces}${intentSpaces}}${newLine}`
        temp += `${intentSpaces}${intentSpaces}${className}(${newLine}`
        temp += Object.keys(obj).map(k => {
            let prop = ''
            prop += intentSpaces
            prop += intentSpaces
            prop += intentSpaces
            prop += getType(k, obj[k])
            prop += `.deserialize(dms.get("${k}"))`
            return prop
        }).join(`,${newLine}`)
        temp += `${newLine}`
        temp += `${intentSpaces}${intentSpaces})${newLine}`
        temp += `${intentSpaces}}${newLine}`
        temp += `}${newLine}`
        res.unshift(temp)
    }
    addClass(json, rootClassName)

    let result = `import serialization.serialization.*${newLine}`
    if (config.generateExample) {
        result += `import encoding.json.*${newLine}`
    }
    result += res.join(newLine)

    if (config.generateExample) {
        result += `${newLine}func parse() {${newLine}`
        result += `${intentSpaces}let jv = JsonValue.fromStr("""${newLine}`
        result += `${intentSpaces}${intentSpaces}${JSON.stringify(json)}${newLine}`
        result += `${intentSpaces}""")${newLine}`
        result += `${intentSpaces}let dm = DataModel.fromJson(jv)${newLine}`
        result += `${intentSpaces}let r = ${rootClassName}.deserialize(dm)${newLine}`
        result += `${intentSpaces}let newDm = r.serialize()${newLine}`
        result += `${intentSpaces}println(newDm.toJson().toJsonString())${newLine}`
        result += `}${newLine}`
    }
    return result
}
export const generate = (json: string | any, config: GenerateConfig = {}): string => {
    if (typeof json === 'string') {
        try {
            return generateFromJson(JSON.parse(json), config)
        } catch (_) {
            return ''
        }
    }
    return generateFromJson(json, config)
}